package self.vector.ding.dao.mybatis.generator.plugin;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.PluginAdapter;
import org.mybatis.generator.api.dom.OutputUtilities;
import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
import org.mybatis.generator.api.dom.java.Interface;
import org.mybatis.generator.api.dom.java.JavaVisibility;
import org.mybatis.generator.api.dom.java.Method;
import org.mybatis.generator.api.dom.java.Parameter;
import org.mybatis.generator.api.dom.java.TopLevelClass;
import org.mybatis.generator.api.dom.xml.Attribute;
import org.mybatis.generator.api.dom.xml.Document;
import org.mybatis.generator.api.dom.xml.TextElement;
import org.mybatis.generator.api.dom.xml.XmlElement;
import org.mybatis.generator.codegen.mybatis3.MyBatis3FormattingUtilities;
import org.mybatis.generator.config.GeneratedKey;
import org.mybatis.generator.internal.util.StringUtility;

public abstract class AbstractInsertBatchPlugin extends PluginAdapter {
	
	private final String InsertBatchMethodName = "insertBatch";
	
	public boolean validate(List<String> warnings) {
		return true;
	}

	public boolean clientGenerated(Interface interfaze, TopLevelClass topLevelClass,
			IntrospectedTable introspectedTable) {
		Method insertBatchMethod = generateMethod(topLevelClass, introspectedTable, InsertBatchMethodName);
		if (generateInsertBatchMethod(insertBatchMethod)) {
			interfaze.addMethod(insertBatchMethod);
		}
		return true;
	}

	public abstract boolean generateInsertBatchMethod(Method method);
	
	public Method generateMethod(TopLevelClass topLevelClass, 
			IntrospectedTable introspectedTable, String methodName) {
		Set<FullyQualifiedJavaType> importedTypes = new TreeSet<FullyQualifiedJavaType>();
		importedTypes.add(FullyQualifiedJavaType.getNewListInstance());
		
		FullyQualifiedJavaType parameterType = FullyQualifiedJavaType.getNewListInstance();
		FullyQualifiedJavaType recordType = introspectedTable.getRules().calculateAllFieldsClass();
		importedTypes.add(parameterType);
		parameterType.addTypeArgument(recordType);
		
		Method method = new Method();
		method.setName(methodName);
		method.setVisibility(JavaVisibility.PUBLIC);
		method.setReturnType(FullyQualifiedJavaType.getIntInstance());
		method.addParameter(new Parameter(parameterType, "records"));
		this.context.getCommentGenerator().addGeneralMethodComment(method, introspectedTable);
		return method;
	}

	public boolean sqlMapDocumentGenerated(Document document, IntrospectedTable introspectedTable) {
		XmlElement parentElement = document.getRootElement();
		XmlElement insertElement = new XmlElement("insert"); //$NON-NLS-1$
		XmlElement foreachElement = new XmlElement("foreach"); //$NON-NLS-1$
		generateInsertBatchXmlDocument(introspectedTable, insertElement, foreachElement);
		if (generateInsertBatchXmlElement(insertElement, foreachElement)) {
			parentElement.addElement(insertElement);
		}
		return true;
	}
	
	protected abstract boolean generateInsertBatchXmlElement(XmlElement insertElement, 
			XmlElement foreachElement);
	
	protected abstract String getColumnsPrefix();
	protected abstract String getColumnsSuffix();
	
	protected abstract String getValuesPrefix();
	protected abstract String getValuesSuffix();
	protected abstract String getForeachSeparator();

	public void generateInsertBatchXmlDocument(IntrospectedTable introspectedTable, 
			XmlElement insertElement, XmlElement foreachElement) {
        insertElement.addAttribute(new Attribute("id", InsertBatchMethodName)); //$NON-NLS-1$
        insertElement.addAttribute(new Attribute("parameterType", //$NON-NLS-1$
                FullyQualifiedJavaType.getNewListInstance().getFullyQualifiedName()));
        context.getCommentGenerator().addComment(insertElement);

        GeneratedKey gk = introspectedTable.getGeneratedKey();
        if (gk != null) {
            IntrospectedColumn introspectedColumn = introspectedTable
                    .getColumn(gk.getColumn());
            // if the column is null, then it's a configuration error. The
            // warning has already been reported
            if (introspectedColumn != null) {
                if (gk.isJdbcStandard()) {
                    insertElement.addAttribute(new Attribute("useGeneratedKeys", "true")); //$NON-NLS-1$ //$NON-NLS-2$
                    insertElement.addAttribute(new Attribute("keyProperty", introspectedColumn.getJavaProperty())); //$NON-NLS-1$
                } else {
                    insertElement.addElement(getSelectKey(introspectedColumn, gk));
                }
            }
        }

        foreachElement.addAttribute(new Attribute("collection", "list"));
        foreachElement.addAttribute(new Attribute("item", "item"));
        foreachElement.addAttribute(new Attribute("index", "index"));
        foreachElement.addAttribute(new Attribute("separator", getForeachSeparator()));
        foreachElement.addElement(new TextElement(getValuesPrefix()));
        StringBuilder insertClause = new StringBuilder();
        StringBuilder valuesClause = new StringBuilder();

        insertClause.append("insert into "); //$NON-NLS-1$
        insertClause.append(introspectedTable.getFullyQualifiedTableNameAtRuntime());
        insertElement.addElement(new TextElement(insertClause.toString() + " " + getColumnsPrefix()));

        insertClause.setLength(0);
        List<String> valuesClauses = new ArrayList<String>();
        Iterator<IntrospectedColumn> iter = introspectedTable.getAllColumns().iterator();
        //insertClause.append("  ");
        //valuesClause.append("  ");
        OutputUtilities.xmlIndent(insertClause, 2);
        OutputUtilities.xmlIndent(valuesClause, 2);
        while (iter.hasNext()) {
            IntrospectedColumn introspectedColumn = iter.next();
            if (introspectedColumn.isIdentity()) {
                // cannot set values on identity fields
                continue;
            }

            insertClause.append(MyBatis3FormattingUtilities
                    .getEscapedColumnName(introspectedColumn));
            valuesClause.append(MyBatis3FormattingUtilities
                    .getParameterClause(introspectedColumn, "item."));
            if (iter.hasNext()) {
                insertClause.append(", "); //$NON-NLS-1$
                valuesClause.append(", "); //$NON-NLS-1$
            }

            if (valuesClause.length() > 80) {
                insertElement.addElement(new TextElement(insertClause.toString()));
                insertClause.setLength(0);
                OutputUtilities.xmlIndent(insertClause, 2);

                valuesClauses.add(valuesClause.toString());
                valuesClause.setLength(0);
                OutputUtilities.xmlIndent(valuesClause, 2);
            }
        }
        
        if (StringUtility.stringHasValue(insertClause.toString().trim())) {
        	insertElement.addElement(new TextElement(insertClause.toString()));
        }
        insertElement.addElement(new TextElement(getColumnsSuffix()));
        if (StringUtility.stringHasValue(valuesClause.toString().trim())) {
        	valuesClauses.add(valuesClause.toString());
        }

        for (String clause : valuesClauses) {
            foreachElement.addElement(new TextElement(clause));
        }
        foreachElement.addElement(new TextElement(getValuesSuffix()));
        insertElement.addElement(foreachElement);
	}

	/**
     * This method should return an XmlElement for the select key used to
     * automatically generate keys.
     * 
     * @param introspectedColumn
     *            the column related to the select key statement
     * @param generatedKey
     *            the generated key for the current table
     * @return the selectKey element
     */
    protected XmlElement getSelectKey(IntrospectedColumn introspectedColumn,
            GeneratedKey generatedKey) {
        String identityColumnType = introspectedColumn
                .getFullyQualifiedJavaType().getFullyQualifiedName();

        XmlElement answer = new XmlElement("selectKey"); //$NON-NLS-1$
        answer.addAttribute(new Attribute("resultType", identityColumnType)); //$NON-NLS-1$
        answer.addAttribute(new Attribute(
                "keyProperty", introspectedColumn.getJavaProperty())); //$NON-NLS-1$
        answer.addAttribute(new Attribute("order", //$NON-NLS-1$
                generatedKey.getMyBatis3Order())); 
        
        answer.addElement(new TextElement(generatedKey
                        .getRuntimeSqlStatement()));

        return answer;
    }
}